package com.tianji.learning.service.impl;

import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.entity.po.PointsBoard;
import com.tianji.learning.entity.query.PointsBoardQuery;
import com.tianji.learning.entity.vo.PointsBoardItemVO;
import com.tianji.learning.entity.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.TableInfoContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.tianji.learning.constants.LearningConstants.POINTS_BOARD_TABLE_PREFIX;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author lemon
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    private final StringRedisTemplate stringRedisTemplate;

    private final UserClient userClient;

    /**
     * 查询学霸天梯榜
     *
     * @param query
     * @return PointsBoardVO
     */
    @Override
    public PointsBoardVO queryPointsBoard(PointsBoardQuery query) {
        // 1.获取当前登录用户id
        Long userId = UserContext.getUser();

        // 2、判断是查询当前赛季还是历史赛季 根据query.season 判断是查询redis还是db
        // 为true为当前赛季，false为历史赛季
        boolean isCurrent = query.getSeason() == null || query.getSeason() == 0; // 当前赛季

        // 拼装key
        LocalDateTime now = LocalDateTime.now();
        String format = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + format;

        // 3、查询我的排名和积分 根据query.season  判断查询redis还是db
        PointsBoard pointsBoard = null;
        if (isCurrent) {
            pointsBoard = queryMyRankAndPoints(key, query, userId);
        } else {
            // 查询那个赛季
            // 赛季id
            Long seasonId = query.getSeason();
            // 很有可能为null 因为当时我没有参加这个赛季
            pointsBoard = queryMyHistoryRankAndPoints(seasonId, query, userId);
        }

        List<PointsBoard> boardList = null;
        // 4、分页查询赛季列表 根据query.season  判断查询redis还是db
        if (isCurrent) {
            // 查询当前赛季
            boardList = queryCurrentSeason(key, query.getPageNo(), query.getPageSize());
        } else {
            // 查询历史赛季
            // 查询那个赛季
            // 赛季id
            Long seasonId = query.getSeason();
            boardList = queryHistorySeason(query, seasonId);
        }
        // 5、封装返回结果
        PointsBoardVO pointsBoardVO = new PointsBoardVO();
        pointsBoardVO.setRank(pointsBoard.getRank() == null ? 0 : pointsBoard.getRank());
        pointsBoardVO.setPoints(pointsBoard.getPoints() == null ? 0 : pointsBoard.getPoints());
        List<PointsBoardItemVO> itemVOList = CollUtils.newArrayList();

        if (CollUtils.isEmpty(boardList)) {
            pointsBoardVO.setBoardList(itemVOList);
            return pointsBoardVO;
        }


        // 远程调用User服务获取用户信息
        List<Long> userIds = boardList.stream().map(PointsBoard::getUserId).collect(Collectors.toList());
        List<UserDTO> userDTOList = userClient.queryUserByIds(userIds);
        if (CollUtils.isEmpty(userDTOList)) {
            log.error("用户信息不存在，userIds:{}", userIds);
            throw new BizIllegalException("用户信息不存在");
        }
        // 转为map
        // key:userId value:UserDTO
        Map<Long, UserDTO> userDTOMap = userDTOList.stream().collect(Collectors.toMap(UserDTO::getId, userDTO1 -> userDTO1));


        // 封装排行榜列表
        for (PointsBoard board : boardList) {
            PointsBoardItemVO itemVO = new PointsBoardItemVO();
            itemVO.setPoints(board.getPoints());
            itemVO.setRank(board.getRank());
            UserDTO userDTO = userDTOMap.get(board.getUserId());
            if (userDTO != null) {
                itemVO.setName(userDTO.getName() == null ? "" : userDTO.getName());
            }
            itemVOList.add(itemVO);
        }

        pointsBoardVO.setBoardList(itemVOList);
        return pointsBoardVO;
    }

    /**
     * 创建表  points_board_sessionId
     *
     * @param seasonId
     */
    @Override
    public void createPointsBoardTableBySeason(Integer seasonId) {
        getBaseMapper().createPointsBoardTable(POINTS_BOARD_TABLE_PREFIX + seasonId);
    }

    /**
     * 将redis中的数据存入数据库
     *
     * @param pointsBoardList
     */
    @Override
    @Transactional
    public void savePointsBoard2DB(List<PointsBoard> pointsBoardList) {
        this.saveBatch(pointsBoardList);
    }


    /**
     * 查询我的排名和积分-不是当前赛季  查询db
     *
     * @param seasonId
     * @param query
     * @param userId
     * @return
     */
    private PointsBoard queryMyHistoryRankAndPoints(Long seasonId, PointsBoardQuery query, Long userId) {
        // 传入动态表名
        TableInfoContext.setInfo(POINTS_BOARD_TABLE_PREFIX + seasonId);

        // 根据userId查询
        PointsBoard pointsBoard = this.lambdaQuery()
                .select(PointsBoard::getId, PointsBoard::getUserId, PointsBoard::getPoints)
                // 注意这里就没有sessionId 因为是分表查询
                .eq(PointsBoard::getUserId, userId)
                .one();

        // 删除动态表名
        TableInfoContext.remove();
        return pointsBoard;
    }

    /**
     * 历史查询列表
     *
     * @param query
     * @param seasonId
     * @return
     */
    private List<PointsBoard> queryHistorySeason(PointsBoardQuery query, Long seasonId) {
        if ((query.getPageNo() - 1) * query.getPageSize() > 100) {
            throw new BizIllegalException("只显示前100名");
        }
        // 根据赛季id查询
        // 传入动态表名
        TableInfoContext.setInfo(POINTS_BOARD_TABLE_PREFIX + seasonId);
        // 并且是分页查询
        List<PointsBoard> list = this.lambdaQuery()
                // 只能查询这三个字段
                .select(PointsBoard::getId, PointsBoard::getUserId, PointsBoard::getPoints)
                // 这里无序排序
                // 因为id就是排名
                .last("limit " + (query.getPageNo() - 1) * query.getPageSize() + "," + query.getPageSize())
                .list();

        // 删除动态表名
        TableInfoContext.remove();
        return list;
    }

    /**
     * 分页查询赛季列表 当前赛季
     *
     * @param key
     * @param pageNo
     * @param pageSize
     * @return
     */
    public List<PointsBoard> queryCurrentSeason(String key, Integer pageNo, Integer pageSize) {
        // 去redis查询
        // 分数从高到低
        // start = (pageNo - 1) * pageSize
        // end = start + pageSize - 1
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, (long) (pageNo - 1) * pageSize, (long) (pageNo - 1) * pageSize + pageSize - 1);
        // 封装返回结果
        if (CollUtils.isEmpty(typedTuples)) {
            return CollUtils.emptyList();
        }


        // TODO 重要
        // 这里因为是分页的 所以rank是每一页的开始位置，不能固定
        int rank = (pageNo - 1) * pageSize + 1;
        // 因为是从高到低，因此需要倒序
        // rank = 1 代表第一名
        // 封装vo
        List<PointsBoard> list = CollUtils.newArrayList();
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {

            // 得到用户id
            String userId = typedTuple.getValue();
            // 得到积分
            Double score = typedTuple.getScore();
            if (StringUtils.isBlank(userId) || score == null) {
                continue;
            }

            // 封装vo
            PointsBoard vo = new PointsBoard();
            vo.setPoints(score.intValue());
            vo.setUserId(Long.valueOf(userId));
            // 排名

            vo.setRank(rank++);
            list.add(vo);
        }

        return list;

    }

    private PointsBoard queryMyRankAndPoints(String key, PointsBoardQuery query, Long userId) {
        // 1.绑定key
        BoundZSetOperations<String, String> ops = stringRedisTemplate.boundZSetOps(key);

        // 这个是我会用的方法
//        stringRedisTemplate.opsForZSet().score(key, userId.toString());
//        stringRedisTemplate.opsForZSet().reverseRank(key, userId.toString());

        // 获取到我的积分
        Double points = ops.score(userId.toString());

        // 获取到我的排名
        // 默认是升序，从小到大  现在是从大到小，降序
        // 从0开始 因此需要 + 1
        Long rank = ops.reverseRank(userId.toString());
        PointsBoard p = new PointsBoard();
        p.setPoints(points == null ? 0 : points.intValue());
        p.setRank(rank == null ? 0 : rank.intValue() + 1);
        return p;
    }
}
